#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define OWNADDR "0.0.0.0" // 我自己电脑的ip地址
#define OWNPORT 11111     // 我自己电脑的该程序的端口号

// 设计一个结构体数据类型
struct client
{
    int clientfd[100]; // 已连接上来的客户端的套接字文件描述符
    int count;         // 已连接用户的总数
};

// 定义一个结构体变量,存储和管理连接上来的客户端
struct client clientManager;

void initManager()
{
    // 初始化
    int i;
    for (i = 0; i < 100; i++)
    {
        clientManager.clientfd[i] = -1; //-1代表该位置是空的
    }
    clientManager.count = 0; // 刚开始的时候没有客户端连接，那就是0
}

int main()
{
    int i;
    initManager();

    printf("服务器的IP地址:%s 端口号:%u\n", OWNADDR, OWNPORT);

    // 1、买手机（建立套接字）
    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketfd == -1)
    {
        printf("没钱了....,失败\n");
        return -1;
    }
    // 因为服务器立马退出之后，端口号不会及时释放
    // 此时如果服务器又马上运行，那么端口号会被占用，导致服务器分配端口号失败，连接失败
    // 所以设置端口号可以复用
    int optval = 1;
    setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // 2、绑定自己的电话号码（绑定自己的IP地址 和端口号）
    // 定义一个IPV4结构体变量，初始化自己的IP地址和端口号
    struct sockaddr_in ownAddr;
    ownAddr.sin_family = AF_INET;                 /*地址族  IPV4*/
    ownAddr.sin_port = htons(OWNPORT);            // htons 将本地端口号转为网络端口号
    ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); // 将本地IP地址转为网络IP地址

    bind(socketfd, (struct sockaddr *)&ownAddr, sizeof(struct sockaddr_in));

    // 3、设置铃声(监听) listen
    listen(socketfd, 5);

    // 定义一个IPV4结构体变量，存储连接上来的客户端的IP地址 和 端口号
    struct sockaddr_in clientAddr;
    // 如果你要想要获取对方的IP地址和端口号，第三个参数必须把结构体的大小传递进去
    int len = sizeof(struct sockaddr_in);

    // socketfd 设置成非阻塞属性
    int status = fcntl(socketfd, F_GETFL); // 得到这个套接字文件描述符的属性
    // 将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞
    status |= O_NONBLOCK;
    fcntl(socketfd, F_SETFL, status); // 把变量status的状态设置到文件描述符中

    while (1)
    {
        // 4、坐等电话（非阻塞接收客户端的连接）
        int newClientFd = accept(socketfd, (struct sockaddr *)&clientAddr, &len);
        if (newClientFd != -1)
        {
            printf("有客户端连接上来了............\n");
            // 打印连接上来的客户端的IP地址和端口号，将网络字节序转为 本地字节序
            printf("连接上来的客户端IP:%s 端口号:%u\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
            // 将已经连接上来的客户端的新的文件描述符 存储到数组中

            // newClientFd设置为非阻塞属性
            int status = fcntl(newClientFd, F_GETFL); // 得到这个套接字文件描述符的属性
            // 将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞
            status |= O_NONBLOCK;
            fcntl(newClientFd, F_SETFL, status); // 把变量status的状态设置到文件描述符中

            clientManager.clientfd[clientManager.count++] = newClientFd;
        }
        // 检查已经连接上来的客户端有没有数据发送过来
        for (i = 0; i < clientManager.count; i++)
        {
            char buf[1024] = {0};
            // 重点重点重点：recv 一定是使用 已经连接上来的新客户端的套接字文件描述符 newClientFd
            int ret = recv(clientManager.clientfd[i], buf, sizeof(buf), 0);
            if (ret == 0) // 代表客户端退出
            {
                printf("客户端退出.....\n");
                // 如果你是用的链表，那么客户端退出之后，把客户端对应的该结点删除掉
                break;
            }
            else if (ret > 0) // 有数据过来的时候才打印
            {
                printf("recv:%s\n", buf);
            }
        }

        sleep(10);
    }

    // 5、关闭
    close(socketfd);
    // close(newClientFd);

    return 0;
}